using System;
using System.Collections.Generic;
using System.Linq;

using MathSharp;
using MathSharp.Utils;

namespace MathSharp.Script
{
	public enum OperationOrder: byte {
		None = 0,
		Level1 = 1,
		Level2 = 2,
		Level3 = 3,
		Level4 = 4,
		Level5 = 5,
		Level6 = 6,
		Level7 = 7,
		Level8 = 8,
		Level9 = 9,
		Level10 = 10,
		Level11 = 11,
		Level12 = 12,
		Level13 = 13,
		Level14 = 14,
		Level15 = 15,
		
		Complete = Level15
	}
	
	public delegate Value ValueEvalutor(BaseExpression expression, EvolutionContext ctx);
	
	public abstract class BaseExpression
	{
		public BaseExpression ()
		{
		}
		
		public abstract ValueEvalutor Evalute(EvolutionContext);
		
		protected static BaseExpression ReadOneExpression(ParsableStringReader str, char[] stopOnChars) {
			if (str.EOF)
				return null;
			char firstChar = str.CurrentChar;
			if (stopOnChars != null && stopOnChars.Contains(firstChar))
				return null;

			switch (firstChar) {
				case '(':
					return GroupingExpression.Read(str);
				case ')':
					if (isGrouping)
						return null; // выражение закончилось закрывающей скобкой
					else
						throw new ParseException(null, 0, str.Offset, 0x0, "Встретилась закрывающая скобка без открывающей");
				case 'f':
				case 't':
					return StaticValueExpresstion.ReadBool(str);
				case 'n':
					return StaticValueExpresstion.ReadNull(str);
				case '0':
				case '1':
				case '2':
				case '3':
				case '4':
				case '5':
				case '6':
				case '7':
				case '8':
				case '9':
				case '.':
					return StaticValueExpresstion.ReadNumeric(str);
				case '"':
					return StaticValueExpresstion.ReadString(str);
				case '$':
					return VariableExpression.Read(str);
				case '@':
					return FunctionExpression.Read(str);
				case '+': 
				case '-':
				case '*':
				case '/':
				case '%':
				case '\\':
				case '!':
				case '^':
				case '~':
				case '&':
				case '|':
				case '?':
				case ':':
				case '<':
				case '>':
					return OperationExpression.Read(str);
				case '#': // начался коментарий
					return null;
				default:
					throw new ParseException(null, 0, str.Offset, 0x0, "Встретился неожидаемый символ в выражении: {0}", firstChar);
			}
		}
		
		public static BaseExpression ReadExpression(ParsableStringReader str) {
			return ReadExpression(str, false);
		}
		
		public static BaseExpression ReadExpression(ParsableStringReader str, char[] stopOnChars) {
			BaseExpression fullExpression = null;
			while (true) {
				str.SkipWhitespaceChars();
				if (str.EOF)
					break;
				BaseExpression currExpression = ReadOneExpression(str, stopOnChars);
				if (currExpression == null) // выражение закончилось
					break; 
				if (fullExpression == null) { // это первый операнд записи
					fullExpression = currExpression;
				} else {
					if (!(currExpression is OperationExpression))
					    throw new ParseException(null, 0, str.Offset, 0x0, "В математическом выражении встретился не математический операнд");
					if (!(fullExpression is OperationExpression)) // эсли общее выражение не математический оператор, то делаем его таковым
						fullExpression = OperationExpression.ConvertToOperationExpression(fullExpression);
					(fullExpression as OperationExpression).AddOperationExpression(currExpression as OperationExpression);
				}
			}
			
			return fullExpression;
		}
	}
/*
* операции и их признак (для определени)
* операция                          | признак    | тип                 | класс
* ----------------------------------+------------+---------------------+---------------------
* скобки. приоритет операций        | ()         | value by matchgroup | GroupingExpression
* предполагается bool (true, false) | ft         | value               | StaticValueExpresstion
* предполагается null               | n          | value               | StaticValueExpresstion
* число                             | 0-9.       | value               | StaticValueExpresstion
* строка                            | "          | value               | StaticValueExpresstion
* переменная                        | $          | value               | VaribleExpression
* вызов функции                     | @          | value               | FunctionExpression
* мат. операции                     | +-/*!%^... | matchgroup          | OperationExpression
	 */
	
	public class GroupingExpression: BaseExpression {
		
		public BaseExpression SubExpression {
			get;
			protected set;
		}
		
		public static GroupingExpression Read(ParsableStringReader str) {
			GroupingExpression res = new GroupingExpression();
			res.SubExpression = ReadExpression(str, true);
		}
		
		public override Value Evalute ()
		{
			return SubExpression.Evalute();
		}
	}
	
	public class StaticValueExpresstion: BaseExpression {
		public Value Value {
			get; set;
		}
		
		public override Value Evalute()
		{
			return Value;
		}
	
		public static StaticValueExpresstion ReadBool(ParsableStringReader str) {
			char curChar = str.CurrentChar;
			StaticValueExpresstion res = null;
			if ((curChar == 't' || curChar == 'T') &&
			    str.Peek(4).ToLower() == "true") {
				res = new StaticValueExpresstion();
				res.Value = new BoolValue(str.Script, true);
				str.Seek(4);
			} else if ((curChar == 'f' || curChar == 'F') &&
			    str.Peek(5).ToLower() == "false") {
				res = new StaticValueExpresstion();
				res.Value = new BoolValue(str.Script, false);
				str.Seek(4);
			} else {
				throw new ParseException(null, 0, str.Offset, 0x0,
				                         "Выражение {0} не может быть разпознано как bool", str.ReadToWhitespace());
			}
			
			return res;
		}
		
		public static StaticValueExpresstion ReadNull(ParsableStringReader str) {
			char curChar = str.CurrentChar;
			StaticValueExpresstion res = null;
			if ((curChar == 'n' || curChar == 'N') &&
			    str.Peek(4).ToLower() == "null") {
				res = new StaticValueExpresstion();
				res.Value = new NullValue(str.Script);
				str.Seek(4);
			} else {
				throw new ParseException(null, 0, str.Offset, 0x0,
				                         "Выражение {0} не может быть разпознано как null", str.ReadToWhitespace());
			}
			
			return res;
		}
		
		public static StaticValueExpresstion ReadNumeric(ParsableStringReader str) {
			object nval = str.ReadNumericDeclaration(str);
			Type t = nval.GetType();
			if (t.IsAnyInteger()) {
				return new StaticValueExpresstion() { Value = new IntValue(str.Script, nval as int) };
			} else if (t.IsAnyFloatPointNumber()) {
				return new StaticValueExpresstion() { Value = DoubleValue(str.Script, nval as double) };
			} else {
				throw new ParseException(null, 0, str.Offset, 0x0, "Не удалось прочитать значение числа");
			}
		}
		
		public static StaticValueExpresstion ReadString(ParsableStringReader str) {
			return new StaticValueExpresstion() { Value = str.ReadStringDeclaration() };
		}
	}
	
	public class VariableExpression: BaseExpression {
		public Variable Variable {
			get; set;
		}
		
		protected List<BaseExpression> indexes = new List<BaseExpression>();
		public List<BaseExpression> Indexes {
			get {
				return indexes;
			}
		}
		
		public override Value Evalute()
		{
			return Variable.Value;
		}
		
		public static VariableExpression Read(ParsableStringReader str) {
			if (str.CurrentChar != '$')
				throw new ParseException(null, 0, str.Offset, 0x0, "Невозможно прочитать переменную: не верный синтаксис - нет в начале символа $");
			str.Seek(1);
			string name = str.ReadWhile((c, pos) => (c >= 'a' && c <= 'z') ||
			                                        (c >= 'A' && c <= 'Z') ||
			                                        c == '_' ||
			                                        (pos > 0 && c >= '0' && c <= '9'));
			Variable variable = str.Namesapce.TryGetVarible(name);
			if (variable == null)
				throw new ParseException(null, 0, str.Offset, 0x0, "В текущем пространстве не найдена переменная с именем {0}", name);
			str.SkipWhitespaceChars();
			if (str.CurrentChar == '[') { // переменная объявлена со списком индексаторов
				/*while (!str.EOF && str.CurrentChar != ']') {
					BaseExpression exp = ReadExpression(str, new char[] { ',', ']' });
					indexes.Add(exp);
				}*/
				throw new NotImplementedException("Поддержка индексторов у переменных в данный момент не поддерживается");
			}
			
			return new VariableExpression() { Variable  = variable } ;
		}
	}
	
	public class FunctionExpression: BaseExpression {
		public DeclareExecutableCommand Function {
			get; set;
		}
		
		List<BaseExpression> args = new ListFragment();
		
		public List<BaseExpression> Arguments {
			get {
				return args;
			}
		}
		
		public override Value Evalute()
		{
			List<Value> argValues = Arguments.Select(arg => arg.Evalute());
			return Function.ExecuteDeclaration(argValues);
		}
		
		public static FunctionExpression Read(ParsableStringReader str) {
			if (str.CurrentChar != '@')
				throw new ParseException(null, 0, str.Offset, 0x0, "Невозможно прочитать переменную: не верный синтаксис - нет в начале символа $");
			str.Seek(1);
			string name = str.ReadWhile((c, pos) => (c >= 'a' && c <= 'z') ||
			                                        (c >= 'A' && c <= 'Z') ||
			                                        c == '_' ||
			                                        (pos > 0 && c >= '0' && c <= '9'));
			DeclareFunctionCommand func = str.Namesapce.TryGetFunction(name);
			if (func == null)
				throw new ParseException(null, 0, str.Offset, 0x0, "Не найдена переменная с именем {0}", name);
			str.SkipWhitespaceChars();
			if (str.CurrentChar != '(')
				throw new ParseException(null, 0, str.Offset, 0x0, "Ожидается ( для описания аргументов вызова функции");
			str.Seek(1); // пропускаем скобку
			List<BaseExpression> args = new List<BaseExpression>();
			while (true) {
				BaseExpression exp = ReadExpression(str, new char[] {',', ')'});
				if (exp == null || str.EOF)
					throw new ParseException(null, 0, str.Offset, 0x0, "Неожиданное окончание строки. Ожидалась ) для закрытия списка аргументов");
				args.Add(exp);
				if (str.CurrentChar == ')') 
					break;
			}
			
			return new FunctionExpression() { args = args, Function = func };
		}
	}
	
	/// <summary>
	/// класс вычисления математических выражений
	/// </summary>
	public class OperationExpression: BaseExpression {
		List<BaseExpression> argsExpressions = new List<BaseExpression>();
		List<string> operations = new List<string>();
		
		public override Value Evalute ()
		{
			if (argsExpressions.Count != operations.Count)
				throw new ExecuteException(null, 0, 0, 0x0, "Нарушенна целостность выражения операций: количество операндов и операций должно совпадать");
			
			if (argsExpressions.Count == 1) { // оптимизированная логика для одного операнда
				Value res = argsExpressions[0].Evalute();
				if (operations[0] != "") // требуется унарная операция
					res.MakeMathUnaryOperation(operations[0]);
				return res;
			}
			
			if (argsExpressions.Count == 2) { // оптимизированная логика для двух опрандов (без приоритетов)
				Value v1 = argsExpressions[0];
				if (operations[0] != "") // требуется унарная операция
					v1.MakeMathUnaryOperation(operations[0]);
				Value v2 = argsExpressions[1];
				return v1.MakeMathBinaryOperation(operations[1], v2);
			}
			
			// логика для 3 и более опрандов (с учётом приоритетов)
			List<BaseExpression> ccArgsExpressions = new List<BaseExpression>(argsExpressions);
			List<string> ccOperations = new List<string>(operations);
			List<Value> ccValues = new List<Value>(); // вычисленные значения
			List<OperationOrder> ccPriorities = new List<OperationOrder>(); // приоритеты операций
			for (int i = 0; i < ccArgsExpressions.Count; i++) {
				ccValues.Add(null); // заполяем пустыми значениями на объём операндов
				OperationOrder priority = OperationPriorityTable.Instance.TryGet(ccOperations[i]);
				if (priority == OperationOrder.None)
					throw new ExecuteException(null, 0, 0, 0x0, "Для операции {0} небыл задан приоритет вычисления.", ccOperations[i]);
				ccPriorities.Add(priority);
			}
			// вычисляем первую операцию как унарную в первую очередь
			ccValues[0] = ccArgsExpressions[0].Evalute();
			if (ccOperations[0] != "") {
				ccValues[0] = ccValues[0].MakeMathUnaryOperation(ccOperations[0]);
				ccOperations[0] = "";
			}
			// вычисляем зарание то, что записанно в скобках
			for (int i = 1 /* первый оператор опускаем */; i < ccOperations.Count; i++) {
				if (ccArgsExpressions[i] is GroupingExpression)
					ccValues[i] = ccArgsExpressions[i].Evalute();
			}
			for (OperationOrder order = OperationOrder.Level1; order <= OperationOrder.Complete; order++) {
				for (int i = 1; i < ccOperations.Count; i++) {
					if (ccPriorities[i] == order) {
						// val1
						if (ccValues[i-1] == null)
							ccValues[i-1] = ccArgsExpressions[i-1].Evalute();
						// val2
						if (ccValues[i] == null)
							ccValues[i] = ccArgsExpressions[i-1].Evalute();
						// вычисляем
						ccValues[i-1] = ccValues[i-1].MakeMathBinaryOperation(ccOperations[i], ccValues[i]);
						// удаляем записи
						ccArgsExpressions.RemoveAt(i);
						ccOperations.RemoveAt(i);
						ccValues.RemoveAt(i);
						ccPriorities.RemoveAt(i);
						ccArgsExpressions[i-1] = null; // удалёем исходный экспрешен чтобы небыло возможности его перевычислить
						i--; // отшагиваем назад т.к. мы удалили текущую операцию
					}
				}
			}
			if (ccValues.Count != 1)
				throw new ExecuteException(null, 0, 0, 0x0, "Нарушение целостности при вычислении выражения: вычисление законсилось с несколькими результатами");
			return ccValues[0];
		}

		public static OperationExpression ReadWithOperand(ParsableStringReader str) {
			// читаем операцию
			string operation = str.ReadWhile(c => @"+-*/%\!^~&|?:<>".IndexOf(c));
			//TODO: тут надо отдельно обработать тринарный оператор a?b:c
			
			// читаем операнд
			str.SkipWhitespaceChars();
			BaseExpression exp = ReadOneExpression(str, null);
			if (exp == null)
				throw new ParseException(null, 0, str.Offset, 0x0, "Ожидалось окончание математического выражения, но второц операнд небыл найден");
			if (exp is OperationExpression)
				throw new ParseException(null, 0, str.Offset, 0x0, "Совмещение бинарной и унарной опрации (a + -b) в данный момент не поддерживается. Допустимо использование записи через групповой оператор вида a + (-b)");
			
			OperationExpression res = new OperationExpression();
			res.argsExpressions.Add(exp);
			res.operations.Add(operation);
			
			return res;
		}
		
		public void AddOperationExpression(OperationExpression expression) {
			// перенимаем аргументы и операции к нам
			argsExpressions.AddRange(expression.argsExpressions);
			operations.AddRange(expression.operations);
		}
		
		public static OperationExpression ConvertToOperationExpression(BaseExpression expression) {
			OperationExpression res = new OperationExpression();
			res.argsExpressions.Add(expression);
			res.operations.Add(""); // первая операция - "унарное ничегонеделанье"
			
			return res;
		}
	}
	
}

